﻿Imports System.Net
Imports System.Net.Sockets
Imports System.Text

Public Class FrmClient

    ''' <summary>
    ''' 软件字符编码集
    ''' </summary>
    ''' <remarks></remarks>


    Private UTF8Code As Encoding = Encoding.UTF8

    ''' <summary>
    ''' 软件字符编码集
    ''' </summary>
    ''' <remarks></remarks>
    Private EnCode As Encoding = Encoding.Default

    ''' <summary>
    ''' 客户端连接对象
    ''' </summary>
    ''' <remarks></remarks>
    Private mTCPClient As TCPClientEx = Nothing

    ''' <summary>
    ''' 连接状态
    ''' </summary>
    ''' <remarks></remarks>
    Private mConn As Boolean = False

    ''' <summary>
    ''' 压力测试模式
    ''' </summary>
    ''' <remarks></remarks>
    Private mTestMode As Boolean

    ''' <summary>
    ''' 生成的客户端列表
    ''' </summary>
    ''' <remarks></remarks>
    Private mTestClients As Collection

    ''' <summary>
    ''' 最大需要创建的连接数
    ''' </summary>
    ''' <remarks></remarks>
    Private mCreateMaxCount As Integer

    Private Sub butClear_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butClear.Click
        TxtDebug.Text = String.Empty
    End Sub

    Private Sub butConnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butConnect.Click
        If Not mConn Then
            '开始准备连接
            Dim lServerPort As Long
            Dim sServer As String

            If Not Long.TryParse(TxtPort.Text, lServerPort) Then
                MsgBox("请输入正确的服务器端口号！", 64, "提示")
                Return
            End If

            sServer = txtServer.Text
            If String.IsNullOrEmpty(sServer) Then
                MsgBox("请输入正确的服务器地址！", 64, "提示")
                Return
            End If


            mTCPClient = New TCPClientEx(sServer, lServerPort)
            AddHandler mTCPClient.OnStateChanage, AddressOf Client_OnStateChanage
            AddHandler mTCPClient.OnDataReceive, AddressOf Client_OnDataReceive
            AddHandler mTCPClient.OnSendComplete, AddressOf Client_OnSendComplete

            mTCPClient.BeginConnectEx()
            SetConnectState(True)
        Else
            Call CloseSocket(True)

            SetConnectState(False)

            SetDebugText("客户端关闭连接！")
        End If
    End Sub

    ''' <summary>
    ''' TCP状态发生变化时触发的事件
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="NowState"></param>
    ''' <remarks></remarks>
    Private Sub Client_OnStateChanage(ByVal sender As Object, ByVal NowState As TCPClientEx.SocketStateEnum)
        Dim oTCP As TCPClientEx = sender

        Select Case NowState
            Case TCPClientEx.SocketStateEnum.eErr
                SetDebugText(oTCP.ErrorCodeToString(oTCP.ErrCode))
                Call CloseSocket(oTCP)
            Case TCPClientEx.SocketStateEnum.eCloseed
                SetDebugText("服务器已关闭连接！")
                Call CloseSocket(oTCP)

            Case TCPClientEx.SocketStateEnum.eConnected
                mKeepAliveTime = Now
                SetDebugText("连接到服务器成功，远程服务器信息：" & oTCP.toRemoteString & "，本地连接参数：" & oTCP.toLocalString)
                If Not ChkKeepAlive.Checked Then oTCP.OpenKeepAlive(3500, 5000) '开启心跳包
                SetConnected(True)
            Case TCPClientEx.SocketStateEnum.eConnecting
                '输出调试信息
                SetDebugText("DNS解析完毕，解析结果：" & oTCP.toDNSString & "。")
                SetDebugText("开始尝试连接到服务器...")
            Case TCPClientEx.SocketStateEnum.eSearchDNS
                SetDebugText("开始使用DNS解析网络地址：" & oTCP.ServerAddr)
        End Select
    End Sub

    ''' <summary>
    ''' 发送指令处理完毕后的事件
    ''' </summary>
    ''' <param name="oClient"></param>
    ''' <param name="oUserPar"></param>
    ''' <param name="iSendByteCount"></param>
    ''' <param name="SendDate"></param>
    ''' <param name="SendCompleteDate"></param>
    ''' <remarks></remarks>
    Private Sub Client_OnSendComplete(ByVal oClient As TCPClientEx, ByVal oUserPar As Object, ByVal iSendByteCount As Integer, ByVal SendDate As Date, ByVal SendCompleteDate As Date)
        Dim sSend As String = String.Empty '接收到的数据明文

        If TmrSend.Enabled Then
            mSendCount += 1
            mSendDataCount += iSendByteCount
            If (Date.Now - mShowDataTime).Seconds >= 1 Then
                Me.Invoke(Sub()
                              mShowDataTime = Date.Now
                              TxtSendDataCount.Text = mSendDataCount & " bytes，次数：" & mSendCount
                          End Sub)
            End If

            Return
        End If


        mKeepAliveTime = Now

        If oUserPar.GetType().ToString = "System.Byte[]" Then
            Dim bData() As Byte
            bData = oUserPar
            'If bData.Count = 1 Then
            '    If bData(0) = &H7E Then Return
            'End If

            If ChkHex.Checked = False Then
                sSend = EnCode.GetString(bData)
            Else
                sSend = ModTool.ByteToHex(bData)
            End If
        Else
            sSend = oUserPar.ToString
        End If



        Me.Invoke(Sub()
                      TxtSend.Focus()
                  End Sub
        )

        DebugClientSend(oClient, sSend, iSendByteCount)
    End Sub

    ''' <summary>
    ''' 有服务器指令到达的事件
    ''' </summary>
    ''' <param name="oClient"></param>
    ''' <param name="bRead"></param>
    ''' <remarks></remarks>
    Private Sub Client_OnDataReceive(ByVal oClient As TCPClientEx, ByVal bRead As Byte())
        Dim sRead As String = String.Empty '接收到的数据明文
        If bRead.Count = 1 Then
            If bRead(0) = &H7E Then Return
        End If

        If ChkHex.Checked = False Then
            sRead = EnCode.GetString(bRead)


            '检测是否连接断开消息
            If sRead.IndexOf("@@<Socket:Close>") > -1 Then
                '服务器已关闭连接
                SetDebugText("接收到服务器断线请求....")
                Call CloseSocket(oClient)
                Return
            End If
        Else
            sRead = ModTool.ByteToHex(bRead)

        End If

        mKeepAliveTime = Now

        SetDebugText("接收字节：" & bRead.Length & "；接收：" & sRead)
    End Sub




    ''' <summary>
    ''' 关闭Socket
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub CloseSocket(ByRef oClient As TCPClientEx)
        If mTestMode Then
            'DeleteTestClient(oClient)
        Else
            CloseSocket(False)
            SetConnectState(False)
        End If
    End Sub

    ''' <summary>
    ''' 关闭Socket
    ''' </summary>
    ''' <param name="bSendCmd">指示是否在关闭连接之前往服务器发送连接关闭指令？</param>
    ''' <remarks></remarks>
    Private Sub CloseSocket(ByVal bSendCmd As Boolean)
        If mTCPClient IsNot Nothing Then
            If bSendCmd And mTCPClient.IsConnected Then
                Try
                    'mTCPClient.Sendex(EnCode.GetBytes("@@<Socket:Close>"), "CloseKey")
                Catch ex As Exception
                    Debug.Print("CloseSocket Err")
                End Try
            End If

            mTCPClient.CloseSocket()
            mTCPClient = Nothing
        End If
    End Sub



    ''' <summary> 设置连接状态 </summary>
    Delegate Sub SetConnectedCallback(ByVal bConnected As Boolean)

    ''' <summary> 设置连接状态 </summary>
    Private Sub SetConnected(ByVal bConnected As Boolean)
        If Me.ButSend.InvokeRequired Then
            Dim method As New SetConnectedCallback(AddressOf SetConnected)
            Me.Invoke(method, New Object() {bConnected})
            Return
        End If

        ButSend.Enabled = bConnected

        TxtSend.ReadOnly = Not bConnected
    End Sub

    ''' <summary>
    ''' 往服务器发送数据
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub Send(ByVal data As String)
        '转换明文为字节数组     
        Dim byteData As Byte() = {}
        If ChkHex.Checked = False Then
            byteData = EnCode.GetBytes(data)
        Else
            data = data.Replace(" ", String.Empty).Replace(vbNewLine, String.Empty)

            byteData = ModTool.HexToByte(data)
        End If
        '异步发送  
        mTCPClient.SendEx(byteData, byteData)



    End Sub 'Send    

    ''' <summary>
    ''' 输出发送至客户端的内容
    ''' </summary>
    ''' <param name="oClient"></param>
    ''' <param name="sText"></param>
    ''' <param name="bytesSent"></param>
    ''' <remarks></remarks>
    Private Sub DebugClientSend(ByRef oClient As TCPClientEx, ByVal sText As String, ByVal bytesSent As Integer)
        Dim sValue As String = "发送：%1,发送字节：%2，内容：%3"

        sValue = ReplaceEx(sValue, {oClient.toRemoteString, bytesSent, sText})
        SetDebugText(sValue)

    End Sub



    ''' <summary>
    ''' 设置输出窗口文本的委托
    ''' </summary>
    ''' <param name="text"></param>
    ''' <remarks></remarks>
    Delegate Sub SetDebugTextCallback(ByVal [text] As String)

    ''' <summary>
    ''' 设置输出窗口文本
    ''' </summary>
    ''' <param name="text"></param>
    ''' <remarks></remarks>
    Private Sub SetDebugText(ByVal [text] As String)
        If Me.TxtDebug.InvokeRequired Then
            Dim method As New SetDebugTextCallback(AddressOf SetDebugText)
            Me.Invoke(method, New Object() {[text]})
        Else
            Dim sTxt As String = Format(Date.Now, "HH:mm:ss") & "--" & [text] & vbNewLine & Me.TxtDebug.Text
            If sTxt.Length > 20000 Then sTxt = sTxt.Substring(0, 10000)
            Me.TxtDebug.Text = sTxt

            'Me.TxtDebug.Text =[Text]
        End If
    End Sub



    Delegate Sub SetConnectStateCallback(ByVal bConn As Boolean)
    ''' <summary>
    ''' 设置连接状态
    ''' </summary>
    Private Sub SetConnectState(ByVal bConn As Boolean)
        If Me.butConnect.InvokeRequired Then
            Dim method As New SetConnectStateCallback(AddressOf SetConnectState)
            Me.Invoke(method, New Object() {bConn})
            Return
        End If

        If Not bConn Then
            butConnect.Text = "连接"
            SetConnected(False)
        Else
            butConnect.Text = "断开"
        End If
        mConn = bConn

        txtServer.ReadOnly = bConn
        TxtPort.ReadOnly = bConn
    End Sub

    Private Sub FrmClient_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        e.Cancel = mConn
        If mConn Then
            MsgBox("请先断开连接!")
        End If
    End Sub

    Private Sub FrmClient_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        '设置默认端口
        TxtPort.Text = 8000
        TxtTestPort.Text = 9688


        TxtCreateCount.Text = 100

        '设置默认主机
        Dim ipHostInfo As IPHostEntry = Dns.GetHostEntry(Dns.GetHostName()) '获取本机IP地址
        Dim ipAddress As IPAddress = ipHostInfo.AddressList(0) '摘取本机IP地址
        txtServer.Text = "192.168.1.187" 'ipAddress.ToString  ' ipAddress.ToString

        '初始化窗口状态
        SetConnectState(False)

        TmrKeepAlive.Enabled = ChkKeepAlive.Checked
        TmrKeepAlive.Interval = 1000
    End Sub


    Private Function IsConnect() As Boolean
        If Not mConn Then
            MsgBox("客户端还未连接到服务器！")
            Return False
        End If
        If Not mTCPClient.IsConnected Then
            MsgBox("客户端还未连接到服务器！")
            Return False
        End If

        If mTCPClient.IsSending Then
            MsgBox("客户端正在发送数据，请稍后！")
            Return False
        End If

        If String.IsNullOrEmpty(TxtSend.Text) Then
            MsgBox("请输入需要发送的内容")
            Return False
        End If

        Return True
    End Function

    Private Sub ButSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButSend.Click
        If Not IsConnect() Then
            Return
        End If
        If chkUseUTF8.Checked Then
            If Not Object.ReferenceEquals(EnCode, UTF8Code) Then
                EnCode = UTF8Code
            End If
        Else
            EnCode = System.Text.Encoding.Default
        End If
        Send(TxtSend.Text)
    End Sub

    Private Sub TxtSend_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TxtSend.KeyPress
        Dim iKey As Integer = Convert.ToInt32(e.KeyChar)
        If Not mConn Then Return
        If iKey = 13 Then
            If Not mTCPClient.IsConnected Then Return
            ButSend_Click(Nothing, Nothing)
        End If
    End Sub

    Private Sub ButTest_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButTest.Click

    End Sub


#Region "软件心跳包"
    Private mKeepAliveTime As Date '上一次保活时间

    Private Sub ChkKeepAlive_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ChkKeepAlive.CheckedChanged
        TmrKeepAlive.Enabled = ChkKeepAlive.Checked
    End Sub

    Private Sub TmrKeepAlive_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TmrKeepAlive.Tick
        If mTCPClient Is Nothing Then Return
        If Not mTCPClient.IsConnected() Then Return
        Dim iSec As Integer = DateDiff(DateInterval.Second, mKeepAliveTime, Now)
        If iSec > 20 Then
            Send("7E")
            mKeepAliveTime = Now
        End If
    End Sub
#End Region


    Private mSendDataCount As Long
    Private mSendCount As Long
    Private mShowDataTime As Date

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        If Not IsConnect() Then
            Return
        End If

        TmrSend.Enabled = True
        TmrSend.Interval = 100

        Button1.Enabled = False

        mSendCount = 0
        mSendDataCount = 0
        mShowDataTime = Date.Now
        TxtSendDataCount.Text = 0
    End Sub

    Private Sub TmrSend_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TmrSend.Tick
        If Not mConn Then
            'MsgBox("客户端还未连接到服务器！")
            TmrSend.Enabled = False
            Button1.Enabled = True
            Return
        End If
        If Not mTCPClient.IsConnected Then
            'MsgBox("客户端还未连接到服务器！")
            TmrSend.Enabled = False
            Button1.Enabled = True
            Return
        End If

        If mTCPClient.IsSending Then
            Return
        End If

        If String.IsNullOrEmpty(TxtSend.Text) Then
            Return
        End If
        Send(TxtSend.Text)
    End Sub

    Private Sub TxtSend_TextChanged(sender As Object, e As EventArgs) Handles TxtSend.TextChanged

    End Sub
End Class






